local license =
[[----------------------------------------------------------------------
  **** BEGIN LICENSE BLOCK ****
	A simple flocking simulator written in LUA.

    Copyright (C) 2010
	Eric Fredericksen
	www.pttpsystems.com
	eric@pttpsystems.com

	This file is part of Flocker.

    Flocker is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

  **** END LICENSE BLOCK ****
------------------------------------------------------------------------]]


-- global scope object for our code; let's keep things tidy
flocker = {}
-- yes, redundant, but not bad form
assert (flocker, "Flocker Engine: Failed to find global variable flocker")

-- load up the preconfigured behavioral states

require("flockerutils")


--our list of avians
flocker.birds = {}
flocker.birdCount = 0

flocker.toroidX			= 1				-- world width - updated by windows size on startup
flocker.toroidY			= 1				-- world height - updated by windows size on startup
flocker.toroidZ			= 1

flocker.repulseR		= 25			-- repulsion distance (personal space)
flocker.detectR			= 50			-- attraction distance
flocker.caffeine		= 5			-- jitteryness of the bird direction
flocker.agility			= 3.0			-- maximum turn rate, sort of
flocker.antisociality	= 4.0			-- how much it hates to be crowded
flocker.partisan		= false			-- do birds only flock with others of the same feather color?
flocker.aggressive		= false			-- will birds fight if they think they can win
flocker.deltaPX			= 3				-- our default simulation PX step in milliseconds
flocker.deltaT			= 20 			-- our default simulation time slice in milliseconds
flocker.totalT			= 0				-- milliseconds

--[[ ====== display related stuff ====== ]]

flocker.cursorPos		= vec3.new(1,0,0)	-- dummy variable to be replaced
flocker.cursorDir		= vec3.new(0,1,0)	-- dummy variable to be replaced
flocker.buttonDown		= {}			-- dummy variable to be replaced
flocker.buttonUp		= {}			-- dummy variable to be replaced
flocker.trackCursor		= false			-- is the mouse button down inside the display window
flocker.cursorFocus		= false			-- has the mouse been clicked inside the display window
flocker.trackCamera		= false			-- for camera control

flocker.showRepel 		= false			-- AUX variable for display control
flocker.showDetect 		= false			-- AUX variable for display control
flocker.bgColor			= 1				-- default color for the background
flocker.iterations		= 0

flocker.threeDeeFlocking	= false
flocker.reflectBirds		= false



-- constructor function for a sparse matrix
function flocker.CreateSparseMatrix( binWidth, binHeight, binDepth)

	--[[	Yes, we are using a closure :)
			This saves us from using the colon (object:function() ) syntax.
			If we ever start making these objects in volume then we can switch
			to the other method. That will save the space require for the functions attached.
		]]
	local sparseMatrix = { bx = binWidth, by = binHeight, bz = binDepth, xArray={} }

	function sparseMatrix.binCoords( x, y, z )
		return math.ceil( x / sparseMatrix.bx ), math.ceil( y / sparseMatrix.by ), math.ceil( z / sparseMatrix.bz )
	end

	function sparseMatrix.addItem( x, y, z, newItem )

		local xArray = sparseMatrix.xArray

		local yArray = xArray[x]
		if not yArray then yArray  = {};	xArray[x] = yArray	end

		local zArray = yArray[y]
		if not zArray then	zArray  = {};	yArray[y] = zArray	end

		local bucket = zArray[z]
		if not bucket then	bucket  = {};	zArray[z] = bucket	end

		-- just append it to the table for speed
		table.insert( bucket, newItem )
	end

	-- we can choose to return either nil or an empty list
	-- probably simpler and faster to return nil
	function sparseMatrix.getList( x, y, z )
		-- try to grab the yArray
		local yArray = sparseMatrix.xArray[x]

		local zArray = yArray and yArray[y]

		return zArray and zArray[z]
	end

	return sparseMatrix
end


--[[Use a binning method to improve to O(N*K) for influence calculation
	The big-oh order will be
		N*[expected number of birds within detection range].
	That number will have a limit determined by the repulsion
	distance and detection distance. One determines how big the
	the bins are and the other determines prensity for crowing,
	or bird density per bin.
	]]
function flocker.FlockBirdsOhNK()
	-- count the
	flocker.iterations = flocker.iterations + 1

	flocker.totalT = flocker.totalT + flocker.deltaT

	-- Bins are as wide as the detection circle, so twice detectR
	local binWidth = flocker.detectR -- math.ceil(flocker.detectR/2)

	--[[ accounting matrix
		NOTE: all this data goes away when the function exits
		This represents a two-dimensional array of lists of bird references
		We use the convention of x, y (not row, col)
		]]
	local birdCages = flocker.CreateSparseMatrix( binWidth, binWidth, binWidth )

	-- total number of bins in the matrix
	local xBins, yBins, zBins = birdCages.binCoords( flocker.toroidX, flocker.toroidY, flocker.toroidZ )

	-- for each bird in the entire world
	for __, thisBird in pairs( flocker.birds ) do

		local xBin, yBin, zBin = birdCages.binCoords( thisBird.p[1], thisBird.p[2], thisBird.p[3] )

		birdCages.addItem( xBin, yBin, zBin, thisBird )

	end


	--[[for each bird in the entire world, allow them to
		observe their environment and collect facts
		]]
	for __, thisBird in pairs( flocker.birds ) do

		-- honor request for state change
		if thisBird.__nextState then
			thisBird:SetState( thisBird.__nextState.state, thisBird.__nextState.parameters )
			thisBird.__nextState = nil
		end


		-- prepare for
		thisBird:Reset_OODA()

		-- offer up pointers to all the birds within local area
		local xMinBin, yMinBin, zMinBin = birdCages.binCoords(
				  thisBird.p[1] - flocker.detectR
				, thisBird.p[2] - flocker.detectR
				, thisBird.p[3] - flocker.detectR
				)
		local xMaxBin, yMaxBin, zMaxBin = birdCages.binCoords(
				  thisBird.p[1] + flocker.detectR
				, thisBird.p[2] + flocker.detectR
				, thisBird.p[3] + flocker.detectR
				)
		-- for each nearby bin, let the bird observe the contents
		local neabyBirds = {}
		for x = xMinBin, xMaxBin do

			for y = yMinBin, yMaxBin do

				for z = zMinBin, zMaxBin do

					local list = birdCages.getList(x,y,z) or {}
					thisBird:Observe( list )
				end

			end

		end

	end


	--[[	for each bird in the entire world, allow them to
			change direction, change state, take action, move
		]]
	for __, thisBird in pairs( flocker.birds ) do
		-- ask the bird to do its thing
		thisBird:OrientDecideAct()
	end

	-- age the bird here because this is where death occurrs
	for __, thisBird in pairs( flocker.birds ) do
		thisBird:AgeBird()
	end

end

-- used when the UI is dragging caught birds
function flocker.MoveCaughtBirds()
	-- cause pinned birds to follow the cursor
	for __, bird in pairs( flocker.birds ) do
		if bird.caught then -- the cursor is dragging this one
			vec3.copyTo(bird.p, flocker.cursorPos)
			vec3.jitterPos( bird.p )
			vec3.copyTo(bird.d, flocker.cursorDir)
		end
	end
end

-- make sure a birds position is in view - wrapped on the screen
function flocker.WrapBirds()
	-- iterate over all the birds and wrap thier positions on the torus
	for __, bird in pairs( flocker.birds ) do
		vec3.wrapIt( bird.p, flocker.toroidX, flocker.toroidY, flocker.toroidZ) -- inplace
	end
end

-- invoked by the UI to "catch" a bird with the cursor
function flocker.CatchBirds( netposition )

	local dsquared = flocker.repulseR^2
	-- iterate over all the birds and see if they have been caught
	for __, bird in pairs( flocker.birds ) do
		if dsquared >= vec3.distanceSquared(netposition, bird.p) then
			bird.caught = true
		end
	end
end

function flocker.Move3DBirdsTo2D()

	for __, bird in pairs( flocker.birds ) do
		-- remove any residual 3D stuff
		bird.p[3] = 0
		bird.d[3] = 0
		vec3.normalizeIt( bird.d )
	end

end

-- user is done monkeying with birds
function flocker.ReleaseBirds()
	-- iterate over all the birds and release any that have been caught
	for __, bird in pairs( flocker.birds ) do
		if bird.caught then
			vec3.copyTo(bird.d, flocker.cursorDir)
			bird.caught=false
		end
	end
end

-- to encapsulate all the cleanup
function flocker.ClearBirds()
	flocker.birds		= {}
	flocker.birdCount	= 0
	flocker.iterations	= 0
	flocker.totalT		= 0
end


